using HybridCLR.Editor;
using HybridCLR.Editor.Commands;
using HybridCLR.Editor.Installer;
using HybridCLR.Editor.Settings;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Security.Cryptography;
using System.Text;
using System.Text.RegularExpressions;
using UnityEditor;
using UnityEngine;

public class BuildScript : Editor
{
    private const string buildInfoPath = "buildInfo.json";
    private const string dllToProject = "Assets/StreamingAssets/Code";
    private const string webRequestPath = "Assets/Main/WebRequest.cs";
    private const string fileServer = "FileServer";
    private static readonly string[] addSymbols = new string[]
    {

    };

    [MenuItem("Tools/Mono/BuildWindow")]
    public static void MonoBuildWindow()
    {
        BuildProject(GetInfo(BuildTarget.StandaloneWindows64, false));
    }

    [MenuItem("Tools/Mono/BuildAndroid")]
    public static void MonoBuildAndroid()
    {
        BuildProject(GetInfo(BuildTarget.Android, false));
    }

    [MenuItem("Tools/IL2CPP/BuildWindow")]
    public static void Il2cppBuildWindow()
    {
        BuildProject(GetInfo(BuildTarget.StandaloneWindows64, true));
    }

    [MenuItem("Tools/IL2CPP/BuildAndroid")]
    public static void Il2cppBuildAndroid()
    {
        BuildProject(GetInfo(BuildTarget.Android, true));
    }

    [MenuItem("Tools/IL2CPP/BuildLocalAndroid")]
    public static void Il2cppBuildLocalAndroid()
    {
        var ip = GetLocalAddress();
        ip = UpdateAddress(ip);
        BuildProject(GetInfo(BuildTarget.Android, true));
        var json = EditorPrefs.GetString("BuildProject");
        if(string.IsNullOrEmpty(json))
        {
            UpdateAddress(ip);
        }
        else
        {
            EditorPrefs.SetString("BuildLocalAndroidIp", ip);
        }
    }

    [MenuItem("Tools/IL2CPP/BuildAndroidExport")]
    public static void Il2cppBuildAndroidExport()
    {
        BuildProject(GetInfo(BuildTarget.Android, true, true));
    }

    [MenuItem("Tools/IL2CPP/BuildIos")]
    public static void Il2cppBuildIos()
    {
        BuildProject(GetInfo(BuildTarget.iOS, true));
    }

    [MenuItem("Tools/CompileTest")]
    public static void CompileDll()
    {
        if (!Link.CheckLink())
            return;
        if (!ScriptUpdate.Check())
            return;
        var buildTarget = EditorUserBuildSettings.activeBuildTarget;
        AddSymbols(EditorUserBuildSettings.selectedBuildTargetGroup, true, false);
        HotUpdateDll(buildTarget, true);
        CopyDll(buildTarget, dllToProject);
        UpdateFileMd5("Assets/StreamingAssets");
        var md5Path = Path.Combine("Assets/StreamingAssets/md5.txt");
        if (File.Exists(md5Path))
            File.Copy(md5Path, $"{fileServer}/{buildTarget}/md5.txt", true);
    }

    [MenuItem("Tools/BuildAssetBundle")]
    public static void BuildAssetBundle()
    {
        if (!Link.CheckLink())
            return;
        var buildTarget = EditorUserBuildSettings.activeBuildTarget;
        AddSymbols(EditorUserBuildSettings.selectedBuildTargetGroup, SettingsUtil.Enable, false);
        HotUpdateDll(buildTarget, true);
        CopyDll(buildTarget, dllToProject);
        BuildBundles(buildTarget);
        UpdateFileMd5("Assets/StreamingAssets");
        var md5Path = Path.Combine("Assets/StreamingAssets/md5.txt");
        if (File.Exists(md5Path))
            File.Copy(md5Path, $"{fileServer}/{buildTarget}/md5.txt", true);
    }

    private static string GetLocalAddress()
    {
        var ipEntry = System.Net.Dns.GetHostEntry(System.Net.Dns.GetHostName());
        foreach (var ip in ipEntry.AddressList)
        {
            if (ip.AddressFamily == System.Net.Sockets.AddressFamily.InterNetwork)
            {
                return ip.ToString();
            }
        }
        return "127.0.0.1";
    }

    private static string UpdateAddress(string ip)
    {
        var content = File.ReadAllText(webRequestPath);
        var match = Regex.Match(content, @"\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}");
        if (match.Success)
        {
            content = Regex.Replace(content, @"\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}", ip);
            File.WriteAllText(webRequestPath, content);
            AssetDatabase.Refresh();
            return match.Value;
        }
        return "127.0.0.1";
    }

    private static BuildInfo GetInfo(BuildTarget buildTarget, bool isEndbleHybridCLR, bool exportProject = false)
    {
        if (File.Exists(buildInfoPath))
            return JsonUtility.FromJson<BuildInfo>(File.ReadAllText(buildInfoPath));

        var targetGroup = BuildTargetGroup.Standalone;
        if (buildTarget == BuildTarget.Android)
            targetGroup = BuildTargetGroup.Android;
        else if (buildTarget == BuildTarget.iOS)
            targetGroup = BuildTargetGroup.iOS;

        var localPathName = $"{buildTarget}/{Application.productName}.exe";

        if (buildTarget == BuildTarget.Android)
        {
            if (!exportProject)
                localPathName = $"{buildTarget}/{Application.productName}.apk";
            else
                localPathName = $"{buildTarget}/{Application.productName}";
        }
        else if (buildTarget == BuildTarget.iOS)
            localPathName = buildTarget.ToString();
        Debug.Log("localPathName:" + localPathName);
        return new BuildInfo
        {
            buildTarget = buildTarget,
            buildTargetGroup = targetGroup,
            localPathName = localPathName,
            orientation = UIOrientation.AutoRotation,
            isRoationPortrait = false,
            isRoationDown = false,
            isRoationLeft = true,
            isRoationRight = true,
            isEnableHybridCLR = isEndbleHybridCLR,
            identifier = "com." + Application.productName + ".unity",
            isMono = !isEndbleHybridCLR,
            buildType = BuildType.Debug,
            buildWithDeepProfilingSupport = false,
            ExportProject = exportProject,
            bundleVersion = "1.0.0",
            bundleVersionCode = 1,
            isAssetBundleIncrement = true
        };
    }

    /// <summary>
    /// 构建AB包
    /// </summary>
    private static void BuildBundles(BuildTarget buildTarget, bool isIncrement = true, bool isCopyToStreamingAssets = true)
    {
        AssetDatabase.RemoveUnusedAssetBundleNames();

        string fold = Path.Combine(buildTarget.ToString(), "StreamingAssets");

        if (!isIncrement)
        {
            if (Directory.Exists(fold))
            {
                Directory.Delete(fold);
            }
        }

        if (!Directory.Exists(fold))
        {
            Directory.CreateDirectory(fold);
        }

        var options = BuildAssetBundleOptions.ChunkBasedCompression;
        var result = BuildPipeline.BuildAssetBundles(fold, options, buildTarget);
        if(result == null)
        {
            throw new Exception("build AssetBundle fail!");
        }

        if (isCopyToStreamingAssets)
        {
            copyFile("Assets/StreamingAssets");
            AssetDatabase.Refresh();
        }

        if (Application.platform == RuntimePlatform.WindowsEditor || Application.platform == RuntimePlatform.OSXEditor)
        {
            copyFile($"{fileServer}/{buildTarget}");
        }

        void copyFile(string dir)
        {
            if (!Directory.Exists(dir))
                Directory.CreateDirectory(dir);
            foreach (var file in Directory.GetFiles(fold, "*", SearchOption.AllDirectories))
            {
                if (file.EndsWith(".manifest"))
                    continue;
                var targetPath = Path.Combine(dir, Path.GetFileName(file));
                File.Copy(file, targetPath, true);
                Debug.Log($"{file} to {targetPath}");
            }
        }
    }

    private static void HotUpdateDll(BuildTarget buildTarget, bool developmentBuild = false, bool isGenerateAot = false)
    {
        if (!SettingsUtil.Enable)
        {
            Debug.Log("HybridCLR unenable");
            return;
        }

        InstallerController installer = new InstallerController();
        if (!installer.HasInstalledHybridCLR())
            installer.InstallDefaultHybridCLR();

        if(isGenerateAot)
        {
            Il2CppDefGeneratorCommand.GenerateIl2CppDef();
        }
        CompileDllCommand.CompileDll(buildTarget, developmentBuild);
        if(isGenerateAot)
        {
            StripAOTDllCommand.GenerateStripedAOTDlls(buildTarget);
            AOTReferenceGeneratorCommand.GenerateAOTGenericReference(buildTarget);
            MethodBridgeGeneratorCommand.GenerateMethodBridge(buildTarget);
        }
    }

    private static void CopyDll(BuildTarget buildTarget, string targetPath)
    {
        if (!SettingsUtil.Enable)
            return;

        var hotUpdateDir = SettingsUtil.GetHotUpdateDllsOutputDirByTarget(buildTarget);
        fileMove(hotUpdateDir, "Runtime", HybridCLRSettings.Instance.hotUpdateAssemblies);

        var aotDir = SettingsUtil.GetAssembliesPostIl2CppStripDir(buildTarget);
        fileMove(aotDir, "Aot", HybridCLRSettings.Instance.patchAOTAssemblies);

        if (targetPath.StartsWith("Assets"))
        {
            AssetDatabase.Refresh();

            if (Application.platform == RuntimePlatform.WindowsEditor || Application.platform == RuntimePlatform.OSXEditor)
            {
                CopyDll(buildTarget, $"{fileServer}/{buildTarget}/Code");
            }
        }

        void fileMove(string dir, string prefix, string[] names)
        {
            var fileDir = Path.Combine(targetPath, prefix);
            if (!Directory.Exists(fileDir))
            {
                Directory.CreateDirectory(fileDir);
            }
            foreach (var name in names)
            {
                var filePath = Path.Combine(dir, $"{name}.dll");
                var bytesPath = Path.Combine(fileDir, $"{name}.bytes");
                File.Copy(filePath, bytesPath, true);
                Debug.Log($"{filePath} to {bytesPath}");
            }
        }
    }

    private static void AddSymbols(BuildTargetGroup targetGroup, bool isEnableHybridCLR, bool isRelease)
    {
        SettingsUtil.Enable = isEnableHybridCLR;

        List<string> symbols = new List<string>();
        if (isEnableHybridCLR)
            symbols.Add("ENABLE_HYBRID_CLR");
        if (isRelease)
            symbols.Add("UNITY_RELEASE");

        symbols.AddRange(addSymbols);

        PlayerSettings.SetScriptingDefineSymbolsForGroup(targetGroup, string.Join(";", symbols));
    }

    private static void UpdateFileMd5(string dir)
    {
        var md5Path = Path.Combine(dir, "md5.txt");
        if (File.Exists(md5Path))
            File.Delete(md5Path);

        List<string> files = new List<string>();
        foreach (var file in Directory.GetFiles(dir, "*", SearchOption.AllDirectories))
        {
            if (file.EndsWith(".meta") || file.EndsWith(".manifest"))
                continue;
            var relativePath = Path.GetRelativePath(dir, file);
            files.Add(string.Format("{0}|{1}", relativePath.Replace("\\", "/"), getFileMd5(file)));
        }

        File.WriteAllText(md5Path, string.Join("\n", files));

        if (dir.StartsWith("Assets/"))
        {
            AssetDatabase.Refresh();
        }

        string getFileMd5(string filePath)
        {
            using (var md5 = MD5.Create())
            {
                byte[] hashBytes;
                try
                {
                    using (FileStream stream = File.OpenRead(filePath))
                    {
                        hashBytes = md5.ComputeHash(stream);
                    }
                }
                catch (Exception ex)
                {
                    Debug.LogError("Error calculating MD5 for the file: " + ex.Message);
                    return default;
                }
                StringBuilder sb = new StringBuilder();
                foreach (byte b in hashBytes)
                {
                    sb.AppendFormat("{0:x2}", b);
                }
                return sb.ToString();
            }
        }
    }

    private static void BuildProject(BuildInfo buildInfo)
    {
        if (buildInfo.buildTarget != EditorUserBuildSettings.activeBuildTarget)
        {
            EditorPrefs.SetString("BuildProject", JsonUtility.ToJson(buildInfo));
            Debug.Log($"curBuildTarget:{EditorUserBuildSettings.activeBuildTarget}");
            EditorUserBuildSettings.SwitchActiveBuildTarget(buildInfo.buildTargetGroup, buildInfo.buildTarget);
            Debug.Log($"newBuildTarget:{buildInfo.buildTarget}");
            Debug.Log("Switch Active Build Target");
            return;
        }
        BuildPlayerOptions buildPlayerOptions = new BuildPlayerOptions
        {
            target = buildInfo.buildTarget,
            options = BuildOptions.None,
            targetGroup = buildInfo.buildTargetGroup,
            locationPathName = buildInfo.localPathName
        };

        PlayerSettings.defaultInterfaceOrientation = buildInfo.orientation;
        PlayerSettings.allowedAutorotateToPortrait = buildInfo.isRoationPortrait;
        PlayerSettings.allowedAutorotateToLandscapeLeft = buildInfo.isRoationLeft;
        PlayerSettings.allowedAutorotateToLandscapeRight = buildInfo.isRoationRight;
        PlayerSettings.allowedAutorotateToPortraitUpsideDown = buildInfo.isRoationDown;

        PlayerSettings.gcIncremental = !buildInfo.isEnableHybridCLR;
        PlayerSettings.SetApplicationIdentifier(buildInfo.buildTargetGroup, buildInfo.identifier);

        PlayerSettings.SetScriptingBackend(buildInfo.buildTargetGroup, buildInfo.isMono ? ScriptingImplementation.Mono2x : ScriptingImplementation.IL2CPP);
        PlayerSettings.bundleVersion = buildInfo.bundleVersion;
        PlayerSettings.stripEngineCode = buildInfo.isEnableHybridCLR;

        EditorUserBuildSettings.development = buildInfo.buildType == BuildType.Development;
        EditorUserBuildSettings.connectProfiler = EditorUserBuildSettings.development;
        EditorUserBuildSettings.buildScriptsOnly = EditorUserBuildSettings.development;
        EditorUserBuildSettings.buildWithDeepProfilingSupport = buildInfo.buildWithDeepProfilingSupport;


#if UNITY_ANDROID
        // android 签名文件
        //PlayerSettings.Android.useCustomKeystore = true;
        //PlayerSettings.Android.keystoreName = "";
        //PlayerSettings.Android.keystorePass = "";
        //PlayerSettings.Android.keyaliasName = "";
        //PlayerSettings.Android.keyaliasPass = "";
        PlayerSettings.Android.forceInternetPermission = true;
        PlayerSettings.insecureHttpOption = InsecureHttpOption.AlwaysAllowed;

        EditorUserBuildSettings.androidBuildType = (AndroidBuildType)((int)buildInfo.buildType);
        Debug.Log("EditorUserBuildSettings.androidBuildType:"+ EditorUserBuildSettings.androidBuildType);
        PlayerSettings.Android.targetArchitectures = AndroidArchitecture.ARMv7 | AndroidArchitecture.ARM64;
        EditorUserBuildSettings.exportAsGoogleAndroidProject = buildInfo.ExportProject;
        PlayerSettings.Android.bundleVersionCode = buildInfo.bundleVersionCode;
#endif

        AddSymbols(buildInfo.buildTargetGroup, buildInfo.isEnableHybridCLR, buildInfo.buildType == BuildType.Release);
        HotUpdateDll(buildInfo.buildTarget, buildInfo.buildType == BuildType.Development, true);
        BuildBundles(buildInfo.buildTarget, buildInfo.isAssetBundleIncrement);
        CopyDll(buildInfo.buildTarget, dllToProject);
        UpdateFileMd5("Assets/StreamingAssets");
        Link.Generate(buildInfo.buildTarget);
        Link.CopyLink();
        LinkWindow.Merge();
        Debug.Log("build:" + buildPlayerOptions.locationPathName);
        var buildReport = BuildPipeline.BuildPlayer(buildPlayerOptions);
        if (buildReport.summary.result == UnityEditor.Build.Reporting.BuildResult.Succeeded)
        {
            Debug.Log("Build Succeeded: " + buildPlayerOptions.locationPathName);
            ScriptUpdate.UpdateHash();
            if(Application.platform == RuntimePlatform.WindowsEditor)
            {
                OpenExplorer(buildPlayerOptions.locationPathName);
            }
        }
        else
        {
            throw new System.Exception("Build failed: " + buildReport.summary.totalErrors);
        }
    }

    private static void OpenExplorer(string path)
    {
        path = Path.Combine(Environment.CurrentDirectory, path.Replace("/", @"\"));
        System.Diagnostics.ProcessStartInfo psi = new System.Diagnostics.ProcessStartInfo("Explorer.exe");
        psi.Arguments = "/e,/select," + path;
        System.Diagnostics.Process.Start(psi);
    }


    [InitializeOnLoad]
    private static class ApplicationCompiling
    {
        static ApplicationCompiling()
        {
            EditorApplication.delayCall += () =>
            {
                EditorApplication.update += Update;
            };

            void Update()
            {
                if (EditorApplication.isCompiling)
                    return;

                EditorApplication.update -= Update;

                var json = EditorPrefs.GetString("BuildProject");
                if (!string.IsNullOrEmpty(json))
                {
                    EditorPrefs.DeleteKey("BuildProject");
                    Debug.Log("Script Reload");
                    BuildProject(JsonUtility.FromJson<BuildInfo>(json));

                    var ip = EditorPrefs.GetString("BuildLocalAndroidIp");
                    if (!string.IsNullOrEmpty(ip))
                    {
                        EditorPrefs.DeleteKey("BuildLocalAndroidIp");
                        UpdateAddress(ip);
                    }
                }
            }
        }
    }

    private enum BuildType
    {
        Debug = 0,
        Development = 1,
        Release = 2
    }
    [System.Serializable]
    private struct BuildInfo
    {
        public BuildTarget buildTarget;
        public BuildTargetGroup buildTargetGroup;
        public string localPathName;
        public UIOrientation orientation;
        public bool isRoationPortrait;
        public bool isRoationLeft;
        public bool isRoationRight;
        public bool isRoationDown;
        public bool isEnableHybridCLR;
        public string identifier;
        public bool isMono;
        public BuildType buildType;
        public bool buildWithDeepProfilingSupport;
        public bool ExportProject;
        public string bundleVersion;
        public int bundleVersionCode;
        public bool isAssetBundleIncrement;
    }
}
